using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers.Common;

namespace ContentOverContainer.Common
{
    public static class AnnotationAnalyzer
    {

        /// <summary>
        /// Checks the type of the dest MultiplicityKind.Any.
        /// </summary>
        /// <param name="pOrgType">orginal type</param>
        /// <param name="pNewType">New type set by resolving multiplicity</param>
        /// <author>Harald Binkle</author>
        /// <exception cref="System.ArgumentException">
        /// MultiplicityKind.Any requires a generic type container.;pNewType
        /// or
        /// The generic type does only have one argument.;pNewType
        /// or
        /// pNewType
        /// </exception>
        internal static void CheckDestMultipliciyType(this TypeSyntax pOrgType, TypeSyntax pNewType)
        {
            CheckDestMultipliciyType(pOrgType, MultiplicityKind.Any, pNewType);
        }

        /// <summary>
        /// Checks the type of the dest multipliciy.
        /// </summary>
        /// <param name="pOrgType">orginal type</param>
        /// <param name="pMultiKind">Kind of the multiplicity.</param>
        /// <param name="pNewType">New type set by resolving multiplicity</param>
        /// <author>Harald Binkle</author>
        /// <exception cref="System.ArgumentException">
        /// MultiplicityKind.Any requires a generic type container.;pNewType
        /// or
        /// The generic type does only have one argument.;pNewType
        /// or
        /// pNewType
        /// </exception>
        internal static void CheckDestMultipliciyType(this TypeSyntax pOrgType, MultiplicityKind pMultiKind,
                                                      TypeSyntax pNewType)
        {
            if (pOrgType.Kind == SyntaxKind.TypeVarKeyword) //no need to change anything (type inference)
                return;

            //TODO: check pNewType implementes IList

            switch (pMultiKind)
            {
                case MultiplicityKind.Any:
                    var genType = pNewType as GenericNameSyntax;
                    if (genType == null)
                        throw new ArgumentException("MultiplicityKind.Any requires a generic type container.",
                                                    "pNewType");
                    break;
                case MultiplicityKind.Option:
                //var semType = SemModel.GetTypeInfo(pNewType);
                //if (!(semType.Type.IsReferenceType
                //    || (semType.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T))
                //    )                    
                //    throw new ArgumentException("MultiplicityKind.Optional requires a a nullable type.", "pNewType");
                //break;
                default: //Bare
                    return;
            }
        }

        /// <summary>
        /// Gets the type of the multiplicity.
        /// </summary>
        /// <param name="pAttributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        internal static TypeSyntax GetMultiplicityType(this IEnumerable<AttributeSyntax> pAttributeSyntax,
                                                       TypeSyntax pNodeType, MultiplicityKind pKind)
        {
            foreach (var atts in pAttributeSyntax)
            { 
                if (atts.ArgumentList == null)
                    continue;
                foreach (var arg in atts.ArgumentList.Arguments)
                {
                    var argType = arg.Expression as TypeOfExpressionSyntax;
                    if (argType != null)
                    {
                        if (pKind == MultiplicityKind.Any)
                        {
                            var genSyntaxType = argType.Type as GenericNameSyntax;
                            return BuildGenericNameSyntax(pNodeType, genSyntaxType);
                        }
                        return argType.Type;
                    }

                }
            }
            return null;
        }


        internal static TypeSyntax GetMultiplicityType(this MemberDeclarationSyntax pMemDecl)
        {
            var kind = pMemDecl.GetMultiplicityKind();
            return pMemDecl.GetMultiplicityAttributes().GetMultiplicityType(pMemDecl.GetDeclarationType(), kind);
        }


        public static MultiplicityInfo GetMultiplicityInfo(this IEnumerable<AttributeSyntax> pAttributeSyntax,
                                                           TypeSyntax pNodeType)
        {
            var result = new MultiplicityInfo();
            result.Kind = pAttributeSyntax.GetMultiplicityKind();
            if (result.Kind == MultiplicityKind.Any)
                result.Type = pAttributeSyntax.GetMultiplicityType(pNodeType, result.Kind);
            return result;
        }


        /// <summary>
        /// Converts the "type" into the generic type annotated  
        /// </summary>
        /// <param name="type"></param>
        /// <param name="annotatedType"></param>
        /// <returns></returns>
        private static GenericNameSyntax BuildGenericNameSyntax(TypeSyntax type, GenericNameSyntax annotatedType)
        {
            SeparatedSyntaxList<TypeSyntax> typeSyntaxes = new SeparatedSyntaxList<TypeSyntax>();
            typeSyntaxes = typeSyntaxes.Add(type);
            TypeArgumentListSyntax newTypeArguments = annotatedType.TypeArgumentList.WithArguments(typeSyntaxes);
            GenericNameSyntax newGenericType = Syntax.GenericName(annotatedType.Identifier, newTypeArguments);
            newGenericType = newGenericType.WithTrailingTrivia(Syntax.Space);
            return newGenericType;
        }

        public static MultiplicityKind GetMultiplicityKind(this MemberDeclarationSyntax pMemDecl)
        {
            var attList = pMemDecl.GetMultiplicityAttributes();
            if (attList == null)
                return MultiplicityKind.None;
            else
                return attList.GetMultiplicityKind();
        }

        /// <summary>
        /// Gets the kind of the multiplicity.
        /// </summary>
        /// <param name="pAttributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        private static MultiplicityKind GetMultiplicityKind(this IEnumerable<AttributeSyntax> pAttributeSyntax)
        {
            foreach (var atts in pAttributeSyntax)
            {
                if (MultiplicityAttribute.EqualsName(atts.Name.ToString()))
                {
                    foreach (var arg in atts.ArgumentList.Arguments)
                    {
                        var argKind = arg.Expression as MemberAccessExpressionSyntax;
                        if ((argKind != null) &&
                            (argKind.GetFirstToken().ToString().Equals(typeof(MultiplicityKind).Name)))
                        {
                            string kind = argKind.GetLastToken().ToString();
                            if (kind.Equals("Any")) return MultiplicityKind.Any;
                        }

                    }
                }
                if (AnyAttribute.EqualsName(atts.Name.ToString())) return MultiplicityKind.Any;
            }
            return MultiplicityKind.Option;
        }

        /// <summary>
        /// Gets the next node in the tree.
        /// </summary>
        /// <param name="pSyntaxNode">The syntax node.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static SyntaxNode GetNextNode(this SyntaxNode pSyntaxNode)
        {
            var childNodeList = pSyntaxNode.Parent.ChildNodes().ToList();
            for (int i = 0; i < childNodeList.Count; i++)
            {
                if (childNodeList[i].Equals(pSyntaxNode) && ((i + 1) < childNodeList.Count))
                    return childNodeList[i + 1];
            }
            return null;
        }


        /// <summary>
        /// Returns information about the type of the node.
        /// </summary>
        /// <param name="pNode">The p node.</param>
        /// <param name="pSemModel">The p sem model.</param>
        /// <returns></returns>
        public static ITypeSymbol GetTypeInfo(this CommonSyntaxNode pNode, ISemanticModel pSemModel)
        {
            var typeInfo = pSemModel.GetTypeInfo(pNode);
            ITypeSymbol nodeType = typeInfo.Type;
            return nodeType;
        }


        public static IEnumerable<AttributeSyntax> GetMultiplicityAttributes(this MemberDeclarationSyntax pMemDecl)
        {
            var property = pMemDecl as PropertyDeclarationSyntax;
            if (property != null) return property.AttributeLists.GetMultiplicityAttributes();
            var field = pMemDecl as FieldDeclarationSyntax;
            if (field != null) return field.AttributeLists.GetMultiplicityAttributes();
            var method = pMemDecl as MethodDeclarationSyntax;
            if (method != null) return method.AttributeLists.GetMultiplicityAttributes();
            throw new ArgumentException("MemberDeclarationSyntax is none of: PropertyDeclarationSyntax, FieldDeclarationSyntax or MethodDeclarationSyntax");
        }


        /// <summary>
        /// Gets the multiplicity attributes out of the syntax list.
        /// </summary>
        /// <param name="pAttributeList">The attribute list.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static IEnumerable<AttributeSyntax> GetMultiplicityAttributes(
            this SyntaxList<AttributeListSyntax> pAttributeList)
        {
            var attList = pAttributeList.SelectMany(a => a.ChildNodes().OfType<AttributeSyntax>())
                                        .Where(
                                            a =>
                                            (MultiplicityAttribute.EqualsName(
                                                ((IdentifierNameSyntax)(a.Name)).Identifier.ValueText)
                                             ||
                                             (AnyAttribute.EqualsName(
                                                 ((IdentifierNameSyntax)(a.Name)).Identifier.ValueText)
                                              ||
                                              (OptionAttribute.EqualsName(
                                                  ((IdentifierNameSyntax)(a.Name)).Identifier.ValueText)))));
            if (attList.Count() == 0)
                return null;
            else return attList;
        }
    }
}
